#ifndef __UVCIN_H__
#define __UVCIN_H__

#include <functional>
#include <string>
#include <mutex>
#include <vector>
#include <map>
#include <errno.h>
#include "json.h"
#include "UsbDevice.h"

namespace Usbstream{


#ifdef __cplusplus
extern "C" {
#endif


using namespace std;
using callback_t = function<bool(void *buffer, int length)>;

/**
 * *************************************************************************
 * 					Constant definition area
 * *************************************************************************
 * */
const string DEVPATH = "/dev/";
const string FULLDEVPATH = "/dev/video%d";
const int BUFFERCOUNT = 3;
const string NODEPREFIX = "video";
/**
 * *************************************************************************
 * 					Helper class definition area
 * *************************************************************************
 * */
class CaptureMode
{
public:
	string format;
	int width;
	int height;
public:
	~CaptureMode(){}
	CaptureMode(){}

	CaptureMode(string format, int width, int height)
	{
		this -> format = format;
		this -> width = width;
		this -> height = height;
	}
	string toString()
	{
		return format + to_string(width) + to_string(height);
	}
};

class BufferType
{
public:
	BufferType(void *start, int length)
	{
		(*this).start = start;
		(*this).length = length;
	}
	~BufferType(){}
public:
	void *start;
	int length;
};



/**
 * *************************************************************************
 * 					Main class definition area
 * *************************************************************************
 * */
class UvcIn : public  UsbDevice{
public:

	/**
	 * Create an object for the video input device
	 * */
	UvcIn(int devicenum);

	//Some operations for resource release are done here
	~UvcIn();

	/**
	 * Object information is obtained, which is encapsulated by MAP
	 * */
	bool getSupportParams(Json::Value &params);

	/**
	 * Setting object information is also done by using map
	 * */
	bool setParams(const Json::Value params);


	/**
	 * For input, you can open and close streams and set callbacks. For output, you can provide a write interface
	 * */
	bool startStream(callback_t cb);

	bool stopStream();

private:

	/**
	 * @param  It mainly stores some information required for initialization
	 * */
	bool initUvcIn(string device_node_name);

	/**
	 * Release resources. Release mapped buffers and shut down device nodes
	 * */
	bool deInitUvcIn();

	/**
	 * Determines whether the incoming device node is an available pull node
	 * @param device_name device node name
	 * @return retrun a bool type value
	 * */
	static bool judgeDevice(string device_node_name);

	/**
	 * Open a separate thread loop to monitor the device node and process a frame of data received
	 * */
	void processStream(callback_t cb);

	/**
	 * 
	 * */
	bool processFrame(callback_t cb);


	/**
	 * A single frame handler that reads each frame needs to handle the buffer and call the media callback
	 * @return The device returns true if it has the above capabilities. The device returns false if it does not have the above capabilities
	 * */
	bool cameraBility();

	/**
	 * You can obtain the device name from the device node
	 * @param device_node_name
	 * @param device_name      out: Device Name of the device corresponding to the node
	 * */
	bool readDeviceName(string device_node_name, string &device_name);

	/**
	 * Gets all formats and resolutions supported by the device input camera
	 * This function saves the obtained device format in a member variable, so it does not return a value
	 * */
	void cameraFormat();

	/**
	 * The buffer allocated in the v4l2 kernel is mapped to user space for use
	 * Return whether the mapping succeeds true if the mapping succeeds, false if the mapping fails. The mapped user space memory is stored in the member variable
	 * */
	bool initMmap();

	bool setCaptureMode(CaptureMode *mode, int rate);

	bool isIFrame(char *buffer);

private:

	//Name of the device node
	string m_dev_node_name;

	//Whether the current state needs to fetch the stream, operate this
	bool m_running = false;

	//Name of the external camera
	string m_cameraName;

	//Whether the device node is connected
	bool m_connect = false;

	//Whether the kernel buffer is mapped
	bool m_mmap;

	//map is used to store the relationship between the name and value of the pull format. key indicates the name of the fmt, and value indicates the value of the specific format of the current fmt
	map<string , unsigned int> m_formatMap;

	//Map the address of the kernel space
	vector<BufferType> m_buffers;

	//frame rate
	int m_fps;

	//File descriptor of the device node
	int m_dev_fd;

	//The stream status flag bit is locked
	mutex m_uvcin_lock;

	//The stream format and width and height supported by this object
	vector<CaptureMode> m_deviceCaptureModes;

	// CaptureMode *m_captureMode;
	bool m_firstStream;
	bool m_processThread = false;

	int m_index = 0;
};

#ifdef __cplusplus
}
#endif

}
#endif
